"""
Abstract class is an extension of a basic class. Like a basic class, an
abstract class has methods and state. Unlike a basic class, it inherits
the `ABC` class and has at least one `abstractmethod`. That means we
cannot create an instance directly from its constructor. In this module,
we will create an abstract class and two concrete classes.

For more about abstract classes, click the link below:

https://www.python.org/dev/peps/pep-3119/
"""

from abc import ABC, abstractmethod


class Employee(ABC):
    """Abstract definition of an employee.

    Any employee can work and relax. The way that one type of employee
    can work and relax is different from another type of employee.
    """

    def __init__(self, name: str, title: str) -> None:
        self.name = name
        self.title = title

    def __str__(self) -> str:
        return self.name

    @abstractmethod
    def do_work(self) -> str:
        """Do something for work."""
        raise NotImplementedError

    @abstractmethod
    def do_relax(self) -> str:
        """Do something to relax."""
        raise NotImplementedError


class Engineer(Employee):
    """Concrete definition of an engineer.

    The Engineer class is concrete because it implements every
    `abstractmethod` that was not implemented above.

    Notice that we leverage the parent's constructor when creating
    this object. We also define `do_refactor` for an engineer, which
    is something that a manager prefers not to do.
    """

    def __init__(self, name: str, title: str, skill: str) -> None:
        super().__init__(name, title)
        self.skill = skill

    def do_work(self) -> str:
        return f"{self} is coding in {self.skill}"

    def do_relax(self) -> str:
        return f"{self} is watching YouTube"

    def do_refactor(self):
        """Do the hard work of refactoring code, unlike managers."""
        return f"{self} is refactoring code"


class Manager(Employee):
    """Concrete definition of a manager.

    The Manager class is concrete for the same reasons as the Engineer
    class is concrete. Notice that a manager has direct reports and
    has the responsibility of hiring people on the team, unlike an
    engineer.
    """

    def __init__(self, name: str, title: str, direct_reports: list) -> None:
        super().__init__(name, title)
        self.direct_reports = direct_reports

    def do_work(self) -> str:
        return f"{self} is meeting up with {len(self.direct_reports)} reports"

    def do_relax(self) -> str:
        return f"{self} is taking a trip to the Bahamas"

    def do_hire(self) -> str:
        """Do the hard work of hiring employees, unlike engineers."""
        return f"{self} is hiring employees"


def main() -> None:
    # Declare two engineers
    engineer_john = Engineer("John Doe", "Software Engineer", "Android")
    engineer_jane = Engineer("Jane Doe", "Software Engineer", "iOS")
    engineers = [engineer_john, engineer_jane]

    # These engineers are employees but not managers
    assert all(isinstance(engineer, Employee) for engineer in engineers)
    assert all(not isinstance(engineer, Manager) for engineer in engineers)

    # Engineers can work, relax and refactor
    assert engineer_john.do_work() == "John Doe is coding in Android"
    assert engineer_john.do_relax() == "John Doe is watching YouTube"
    assert engineer_john.do_refactor() == "John Doe is refactoring code"

    # Declare manager with engineers as direct reports
    manager_max = Manager("Max Doe", "Engineering Manager", engineers)

    # Managers are employees but not engineers
    assert isinstance(manager_max, Employee)
    assert not isinstance(manager_max, Engineer)

    # Managers can work, relax and hire
    assert manager_max.do_work() == "Max Doe is meeting up with 2 reports"
    assert manager_max.do_relax() == "Max Doe is taking a trip to the Bahamas"
    assert manager_max.do_hire() == "Max Doe is hiring employees"


if __name__ == "__main__":
    main()
